﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using VsxFactory.Modeling.VisualStudio;
using System.IO;
using VsxFactory.Modeling.Repository;
using VsxFactory.Modeling.Strategies;
using System.Xml.Linq;
using System.IO.Packaging;
using System.Net;


namespace VsxFactory.Modeling.Repository
{
    public class LocalPackageRepository : ReferencePackageRepository
    {
        private string _packageReferenceFile;
        private IStrategyContext _context;

        public LocalPackageRepository(IStrategyContext context, string kind) : base(context, kind)
        {
            System.Diagnostics.Contracts.Contract.Requires(context != null);
            _context = context;
            var folder = context.Settings.GetPackageFolder(kind);
            _packageReferenceFile = Path.Combine(folder, PackageReferenceFile);
            InitializeData();
        }

        protected virtual void InitializeData()
        {
            // Reconstruction du cache des stratégies
            try
            {
                var doc = GetDocument(true);
                doc.Root.RemoveNodes();
                foreach (var manifest in PackageHelper.EnumeratePackages(Context, Kind))
                {
                    var package = new PackageInfo(manifest);
                    AddPackageInfo(doc, package);
                }
                SaveDocument(doc);
            }
            catch (Exception ex)
            {
                Context.WriteError("Cache initialization error : {0}", ex.Message);
            }
        }        

        public override PackageSource Source
        {
            get { return new PackageSource("Local", Path.GetDirectoryName(_packageReferenceFile)); }
        }

        public override IQueryable<PackageInfo> GetPackages()
        {
            return base.GetPackages();
        }

        public override void InstallPackage(PackageInfo package)
        {
            base.InstallPackage(package);
            if( package.Kind != PackageKind.System)
                DownloadPackage(package);
        }

        protected virtual void DownloadPackage(PackageInfo package)
        {
            string fileName = this.Context.Settings.ResolvePackageFileName(package);
            string folder = this.Context.Settings.ResolvePackageFolder(package);

            // ne pas recharger si le package est identique mais vérifier qu'il a bien été extrait
            bool download = !File.Exists(fileName);
            if (!download)
            {
                var localPackage = PackageHelper.GetManifestFromPackage(fileName);
                if (localPackage == null)
                {
                    download = true;
                }
                else
                {
                    download = localPackage.Version != package.Version;
                }
            }

            if (download)
            {
                Uri source = !String.IsNullOrEmpty(package.RepositorySource) ? new Uri(package.RepositorySource) : Context.Settings.DefaultRepositorySource != null ? Context.Settings.DefaultRepositorySource.SourceAsUri : null;
                if (source != null)
                {
                    RepositoryDataContext dataContext = new RepositoryDataContext(source);
                    var pi = dataContext.Packages.Where(p => p.ID == package.ID && p.Version == package.Version).FirstOrDefault();
                    if (pi != null)
                    {
                        using (var response = dataContext.GetReadStream(pi, "application/octet-stream"))
                        {
                            using (var stream = new FileStream(fileName, FileMode.Create))
                            {
                                response.Stream.CopyTo(stream);
                            }
                        }
                    }
                }
            }

            if( File.Exists(fileName))
                ExtractPackage(package);
        }

        private PackageManifest ExtractPackage(PackageInfo package)
        {
            PackageManifest manifest = null;
            var folder = this.Context.Settings.ResolvePackageFolder(package);
            var packageFileName = this.Context.Settings.ResolvePackageFileName(package);

            Utils.RemoveDirectory(folder);

            using (ZipPackage zipPackage = Package.Open(packageFileName, FileMode.Open, FileAccess.Read) as ZipPackage)
            {
                foreach (ZipPackagePart part in zipPackage.GetParts())
                {
                    string path = Path.Combine(folder, Uri.UnescapeDataString(part.Uri.OriginalString).Replace('/', '\\').TrimStart(new char[] { '\\' }));
                    string directoryName = Path.GetDirectoryName(path);
                    Directory.CreateDirectory(directoryName);
                    byte[] buffer = new byte[4096];
                    using (Stream reader = part.GetStream(FileMode.Open, FileAccess.Read))
                    {
                        if (part.Uri.OriginalString == "/" + PackageHelper.MANIFEST_FILE_NAME)
                        {
                            manifest = PackageHelper.DeserializeManifest(reader);
                            reader.Position = 0;
                        }
                        try
                        {
                            using (Stream writer = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write))
                            {
                                int count = 0;
                                while ((count = reader.Read(buffer, 0, buffer.Length)) > 0)
                                {
                                    writer.Write(buffer, 0, count);
                                }
                            }
                        }
                        catch
                        {
                        
                        }
                    }
                }
            }
            return manifest;
        }

        public override void Uninstall(PackageInfo package)
        {
            System.Diagnostics.Contracts.Contract.Requires(package != null);

            base.Uninstall(package);

            // Suppression du fichier package
            string packageFileName = this.Context.Settings.ResolvePackageFileName(package);
            Utils.DeleteFile(packageFileName);

            // Suppression du répertoire d'extraction du package
            string folder = this.Context.Settings.ResolvePackageFolder(package);
            try
            {
                DirectoryInfo di = new DirectoryInfo(folder);
                var parent = di.Parent;
                Utils.RemoveDirectory(folder);
                // Si le répertoire père est vide, on le supprime aussi
                if (parent.Exists && !parent.EnumerateDirectories().Any() && !parent.EnumerateFiles().Any())
                    parent.Delete();
            }
            catch
            { }
        }

        protected override System.Xml.Linq.XDocument GetDocument(bool createIfNotExists = false)
        {
            // If the file exists then open and return it
            if (File.Exists(_packageReferenceFile))
            {
                using (Stream stream = File.OpenRead(_packageReferenceFile))
                {
                    return XDocument.Load(stream);
                }
            }

            // If it doesn't exist and we're creating a new file then return a
            // document with an empty packages node
            if (createIfNotExists)
            {
                return new XDocument(new XElement("packages"));
            }

            return null;
        }

        protected override void SaveDocument(System.Xml.Linq.XDocument document)
        {
            Directory.CreateDirectory(Path.GetDirectoryName(_packageReferenceFile));
            document.Save(_packageReferenceFile);
        }
    }
}